// Copyright (c) 1996-2002 John Lyon-Smith. All rights reserved.

#pragma once

#include <wxtl\Base.h>
#include <wxtl\File.h>
#include <time.h>
#include <limits.h>
#include <mbstring.h>

//using namespace boost;

namespace wxtl
{
	// read/write a file from/to a string
	#pragma warning(push)
	#pragma warning(disable: 4127)  // conditional expression is constant
	#pragma warning(disable: 4239)  // non-ref to ref type cast
	#pragma warning(disable: 4238)  // class rvalue used as lvalue

	template<class T>
	void _stdcall readFile(const TCHAR *fname, T& s, bool checkForLeadBytes = false) throw(...)
	{
		File file;

		file.open(fname, File::accessRead | File::shareRead);

		const BYTE utf8[3] = {0xEF, 0xBB, 0xBF};	// UTF-8
		const BYTE utf16[2] = {0xFF, 0xFE};			// UTF-16 (Unicode) little-endian
		size_t maxLeadBytes = max(ARRAYSIZE(utf8), ARRAYSIZE(utf16));

		ULONGLONG fileLen = file.size();
		const ULONGLONG maxFileLen = 
			((ULONGLONG)INT_MAX - 1) * sizeof(T::value_type) - 
			(checkForLeadBytes ? maxLeadBytes : 0);
		
		if (fileLen > maxFileLen)
			fileLen = maxFileLen;
			
		if (checkForLeadBytes && fileLen < maxLeadBytes)
		{
			s.erase();
			return;
		}

		bool unicode = (sizeof(T::value_type) == sizeof(wchar_t));
		size_t bytesRead;
		std::auto_ptr<BYTE> buf(new BYTE[(size_t)fileLen + sizeof(T::value_type)]);

		file.read((void*)buf.get(), (size_t)fileLen, &bytesRead);
		
		if (checkForLeadBytes)
		{
			if (memcmp(buf.get(), utf16, ARRAYSIZE(utf16)) == 0)
			{
				if (unicode)
					s.assign(
						(T::pointer)(buf.get() + ARRAYSIZE(utf16)), 
						(bytesRead - ARRAYSIZE(utf16)) / sizeof(T::value_type));
				else
					s = (T&)unicodeToMbcs(
						(const wchar_t*)(buf.get() + ARRAYSIZE(utf16)), 
						(bytesRead - ARRAYSIZE(utf16)) / sizeof(wchar_t));

				return;
			}
			else if (memcmp(buf.get(), utf8, ARRAYSIZE(utf8)) == 0)
			{
				if (unicode)
					s = (T&)mbcsToUnicode(
						(const char*)(buf.get() + ARRAYSIZE(utf8)), 
						(bytesRead - ARRAYSIZE(utf8)) / sizeof(char));
				else
					s.assign((T::pointer)(buf.get() + ARRAYSIZE(utf8)), 
						(bytesRead - ARRAYSIZE(utf8)) / sizeof(T::value_type));

				return;
			}
		}

		// Default is to just treat the file as "whatever" chars
		s.assign((T::pointer)buf.get(), bytesRead / sizeof(T::value_type));
	}
	
	template void _stdcall readFile<string>(const TCHAR *fname, string& s, bool checkForLeadBytes);
	template void _stdcall readFile<wstring>(const TCHAR *fname, wstring& s, bool checkForLeadBytes);

	template<class T>
	void _stdcall writeFile(const TCHAR *fname, const T *data, bool writeLeadBytes = false) throw(...)
	{
		File::create(fname);

		File file;

		file.open(fname, File::accessWrite);

		bool unicode = (sizeof(T) == sizeof(wchar_t));

		if (writeLeadBytes)
		{
			if (unicode)
			{
				const BYTE utf16[2] = {0xFF, 0xFE};			// UTF-16 (Unicode) little-endian
				
				file.write(utf16, ARRAYSIZE(utf16));
			}
			else
			{			
				const BYTE utf8[3] = {0xEF, 0xBB, 0xBF};	// UTF-8

				file.write(utf8, ARRAYSIZE(utf8));
			}
		}

		size_t length = (unicode ? wcslen((const wchar_t*) data) : _mbslen((const unsigned char*)data)) * sizeof(T);
		
		file.write((void*)data, (DWORD)(DWORD_PTR)length);
	}
	
	template void _stdcall writeFile<char>(const TCHAR*, const char*, bool);
	template void _stdcall writeFile<wchar_t>(const TCHAR*, const wchar_t*, bool);

	#pragma warning(pop)

	// String replacement and substitution
	inline bool _stdcall substitute(tstring& s, size_t fromPos, size_t fromLen, const TCHAR *pWith)
	{
		size_t curLen = s.size();

		if (fromPos + fromLen > curLen)
			return false;
		
		tstring sNew = s.substr(0, fromPos) + pWith + s.substr(fromPos + fromLen);

		s = sNew;

		return true;
	}

	inline size_t _stdcall replace(tstring& s, const TCHAR *pFind, const TCHAR *pWith)
	{
		_ASSERTE(pFind);
		size_t findLen = _tcslen(pFind);
		size_t withLen = _tcslen(pWith);
		_ASSERTE(findLen!= 0);
		size_t i = 0;
		size_t count = 0;

		while (i < s.size() && (i = s.find(pFind, i)) != -1)
		{
			substitute(s, i, findLen, pWith);
			count++;
			i += withLen;
		}
		
		return count;	
	}

	// remove matched pair of quotes around a doubly quoted string
	template<typename T>
	inline T _stdcall unquote(const T& s)
	{
		return unquote<T>(s.c_str());
	}

	template string _stdcall unquote<string>(const string&);
	template wstring _stdcall unquote<wstring>(const wstring&);

	template<typename T>
	inline T _stdcall unquote(const typename T::value_type *s)
	{
		bool unicode = (sizeof(T::value_type) == sizeof(wchar_t));
		size_t len = (unicode ? wcslen((wchar_t*)s) : strlen((char*)s));
		
		if (len >= 2 && s[0] == '"' && s[len - 1] == '"')
			return T(s + 1, len - 2);
		else
			return T(s);
	}

	// remove extraneous spaces around both ends of a string
	template<typename T>
	inline T _stdcall trim(const T& s)
	{
		return trim<T>(s.c_str());
	}

	template string _stdcall trim<string>(const string&);
	template wstring _stdcall trim<wstring>(const wstring&);
	
	template<typename T>
	inline T _stdcall trim( const typename T::value_type *s)
	{
		bool unicode = (sizeof(T::value_type) == sizeof(wchar_t));
		size_t len = (unicode ? wcslen((wchar_t*)s) : strlen((char*)s));
		size_t i = 0;
		for (; i < len && isspace(s[i]); i++);
		
		if (i == len)
			return T();

		// If we get here we can assume string has some non-space characters
		size_t j = len - 1;
		for (; j >= 0 && isspace(s[j]); j--);
		
		return T(s + i, j - i + 1);
	}

	template<class T>
	struct less_no_case : public binary_function <T, T, bool> 
	{
		bool operator()(const T& _Left, const T& _Right) const
		{
			return _tcsicmp(_Left.c_str(), _Right.c_str()) < 0;
		}
	};
	
	inline tstring _stdcall toUpper(const TCHAR* s)
	{
		tstring t;
		size_t len = _tcslen(s);
		
		t.resize(len);
		
		for (size_t i = 0; i < len; i++)
			t.at(i) = (TCHAR)toupper(s[i]);
			
		return t;
	}

	inline tstring _stdcall toLower(const TCHAR* s)
	{
		tstring t;
		size_t len = _tcslen(s);
		
		t.resize(len);
		
		for (size_t i = 0; i < len; i++)
			t.at(i) = (TCHAR)tolower(s[i]);
			
		return t;
	}

	// Date/time
	inline tstring _stdcall getDateTimeString(time_t *pTime = NULL)
	{	
		time_t t;

		if (NULL == pTime)
			t = time(NULL);
		else 
			t = *pTime;

		TCHAR buf[128];

		//_tcscpy_s(buf,128, _tctime_s(&t));
		_tctime_s(buf,128,&t);

		buf[_tcslen(buf) - 1] = 0;

		return buf;
	}

	inline string _stdcall unicodeToAscii(const WCHAR* s, size_t len = wstring::npos)
	{
		if (len == string::npos)
			len = wcslen(s);
		
		string t;
		
		t.resize(len);
		
		for (size_t i = 0; i < len; i++)
			t[i] = (char)(s[i] & 0x7f);  // Copy, masking off non-ascii data
			
		return t;
	}
	
	inline string _stdcall unicodeToMbcs(const WCHAR* s, size_t len = wstring::npos)
	{
		if (len == string::npos)
			len = wcslen(s);
		
		std::auto_ptr<char> t(new char[len + 1]);
		
		::WideCharToMultiByte(CP_THREAD_ACP, 0, s, (int)len, t.get(), (int)len + 1, NULL, NULL);
			
		return string(t.get(), len);
	}
	
	inline wstring _stdcall asciiToUnicode(const CHAR* s, size_t len = string::npos)
	{
		if (len == wstring::npos)
			len = strlen(s);

		wstring t;
		
		t.resize(len);
		
		for (size_t i = 0; i < len; i++)
			t[i] = s[i];
		
		return t;
	}
	
	inline wstring _stdcall mbcsToUnicode(const CHAR* s, size_t len = string::npos)
	{
		if (len == wstring::npos)
			len = strlen(s);
			
		std::auto_ptr<wchar_t> t(new wchar_t[len + 1]);
		
		::MultiByteToWideChar(CP_THREAD_ACP, 0, s, (int)len, t.get(), (int)len + 1);
			
		return wstring(t.get(), len);
	}
}
